#ifndef __GLF_NET_SOCKET_H_INCLUDED__
#define __GLF_NET_SOCKET_H_INCLUDED__

#include <string>

namespace glf {

struct AddrIp4 {
	//static const ulong INADDR_ANY = 0;
	union {
		ulong ulongAddr;
		ushort ushortArrayAddr[2];
		uchar ucharArrayAddr[4];
		struct {
			uchar Addr1;
			uchar Addr2;
			uchar Addr3;
			uchar Addr4;
		} uChar;
		struct {
			ushort Addr1;
			ushort Addr2;
		} uShort;
	} Addr;
	ushort port;
	ushort vport;
};

enum NetError {
	NETERROR_INPROGRESS = -1, //not really an error
	NETERROR_NONE = 0,
	NETERROR_NOTINITIALIZED,
	NETERROR_NOTOPENED,
	NETERROR_ALREADYCONNECTED,
	NETERROR_ISALREADYCONNECTING,
	NETERROR_TIMEOUT,
	NETERROR_CLOSED,
	NETERROR_UNKNOWN,
	NETERROR_COUNT
};

class Socket {

public:
	enum Type {
		TYPE_STREAM,
		TYPE_DATAGRAM
	};

	enum Proto {
		PROTOCOL_UDP,
		PROTOCOL_TCP
	};

	enum Option {
		OPTION_NONE			= 0,
		OPTION_BROADCAST	= 1<<0,
		OPTION_REUSEADDR	= 1<<1,
		OPTION_USECRYPTO	= 1<<2,
		OPTION_BLOCKING		= 1<<3,
		OPTION_NODELAY		= 1<<4
	};

public:
	static NetError Init();
	static NetError Deinit();
	static std::string GetHostName();
	static const char *GetAddrIP(const AddrIp4 &addr);
	static AddrIp4 MakeAddr(const char *ip, int port);

public:
	Socket();
	virtual ~Socket(); //virtual ?

	bool IsOpened() const;

	bool OpenTcp(Option option = OPTION_NONE);
	bool OpenUdp(Option option = OPTION_NONE);

	bool Open(Type type, Proto protocol, Option option = OPTION_NONE);

	void Close();

	bool Connect(const AddrIp4& addr);
	bool IsConnected() const;

	bool Bind(const AddrIp4& addr);
	bool Listen(int backlog);
	int Accept(AddrIp4& addr);
	bool Reject(int id);

	int Send(const void* buf, int size, int id = 0);
	int SendTo(const AddrIp4& addr, const void* buf, int size);

	bool IsReadable(int id = 0) const;
	int Receive(void* buf_out, int size, int id = 0);
	int ReceiveFrom(AddrIp4& addr_out, void* buf_out, int size);

	bool GetAddress(AddrIp4& addr);

	NetError GetLastError() const;
	void ClearError();

	template<typename T>
	static T HostToNetwork(T value) {
		return SwapEndian(value);
	}

	template<typename T>
	static T NetworkToHost(T value) {
		return SwapEndian(value);
	}


private:

	Type mType;
	Proto mProtocol;
	Option mOptions;

	int mConnectedCount;
	bool mIsOpened;
	mutable NetError mLastError;

#ifdef _WIN64
	GLF_DECLARE_IMPL(528)
#else
	GLF_DECLARE_IMPL(264)
#endif
};

} //namespace glf

GLF_ENUM_FLAG(glf::Socket::Option)

#endif
